/*
 * Created on Apr 21, 2008
 *
 */
package com.robaone.api.business;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

import com.robaone.util.INIFileReader;
import com.trilead.ssh2.ChannelCondition;
import com.trilead.ssh2.Connection;
import com.trilead.ssh2.Session;

/**
 * @author arobateau
 *
 */
public class RemoteExecute {

	public static final Object CMD = "cmd";
	private String m_host;
	private String m_username;
	private String m_remote_profile;
	private String m_remote_path;
	private int exitval = 0;
	private String m_message;
	private long m_timeout = 3600000;
	@SuppressWarnings("rawtypes")
	private java.util.HashMap m_mnemonics = new HashMap();
	private String m_password;
	public static int mcpmain(String[] args,String[] mcpargs){
		try{
			int retval = 0;
			RemoteExecute re = new RemoteExecute();
			re.RunRemoteExecute(args,System.out);
			retval = re.exitval;
			mcpargs[0] = re.getMessage();
			return retval;
		}catch(Exception e){
			mcpargs[0] = e.getClass().getName()+": "+e.getMessage();
			e.printStackTrace(System.out);
			return 1;
		}
	}
	/**
	 * @return
	 */
	public String getMessage() {
		return this.m_message;
	}
	public static void main(String[] args) {
		RemoteExecute re = new RemoteExecute();
		re.RunRemoteExecute(args,System.out);
	}

	private String replaceMnemonics(String value) {
		try{
			Object[] keys = this.m_mnemonics.keySet().toArray();
			for(int i = 0; i < keys.length;i++){
				Object mnemonic = keys[i];
				String mnemonic_value = (String)this.m_mnemonics.get(mnemonic);
				value = value.replaceAll("%"+mnemonic+"%", mnemonic_value);
			}
			return value;
		}catch(Exception e){
			return value;
		}
	}
	/**
	 * @param args
	 */
	public void RunRemoteExecute(String[] args,OutputStream out) {
		String hostname = null;
		String username = null;
		String password = null;


		try
		{
			this.processArguments(args);
			executeCommand(args);
		}
		catch (IOException e)
		{
			e.printStackTrace(System.err);
			exitval = 2;
			this.m_message = e.getMessage();
			//System.exit(2);
		} catch (InputParamsException e) {
			e.printStackTrace();
			System.err.println("Error: "+ e.getMessage());
			exitval = 1;
			this.m_message = "Error: " + e.getMessage();
			RemoteExecute.showUsage();
		}
		//System.exit(exitval);
	}
	public int executeCommand(HashMap<String,String> args,OutputStream out) throws IOException {
		this.m_remote_path = args.get(REMOTE_PATH);
		this.m_remote_profile = args.get(REMOTE_PROFILE);
		return this.executeCommand(args.get(HOST), args.get(USERNAME), args.get(PASSWORD), args.get(CMD),out);
	}
	private void executeCommand(String[] args) throws IOException {
		String hostname;
		String username;
		String password;
		hostname = this.m_host;
		username = this.m_username;
		password = this.m_password;
		/* Create a connection instance */
		String cmd = "";
		for(int i = 1; i < args.length;i++){
			if(i > 1){
				cmd += " ";
			}
			cmd += this.encodeParameter(args[i]);
		}
		
		executeCommand(hostname, username, password, cmd,System.out);
	}
	private int executeCommand(String hostname, String username,
			String password, String cmd, OutputStream out) throws IOException {
		Connection conn = new Connection(hostname);

		/* Now connect */

		conn.connect();

		/* Authenticate */

		boolean isAuthenticated = conn.authenticateWithPassword(username, password);

		if (isAuthenticated == false)
			throw new IOException("Authentication failed.");

		/* Create a session */

		Session sess = conn.openSession();

		//sess.execCommand("echo \"Huge amounts of text on STDOUT\"; echo \"Huge amounts of text on STDERR\" >&2");
		//System.out.println("Executing Remote Command: \". "+re.m_remote_profile+"; $MICRO/scripts/SecureExecute.sh -r " + cmd + "\"");
		sess.execCommand(". "+this.m_remote_profile+"; "+this.m_remote_path+"SecureExecute.sh -r " + cmd);

		/*
		 * Advanced:
		 * The following is a demo on how one can read from stdout and
		 * stderr without having to use two parallel worker threads (i.e.,
		 * we don't use the Streamgobblers here) and at the same time not
		 * risking a deadlock (due to a filled SSH2 channel window, caused
		 * by the stream which you are currently NOT reading from =).
		 */

		/* Don't wrap these streams and don't let other threads work on
		 * these streams while you work with Session.waitForCondition()!!!
		 */

		InputStream stdout = sess.getStdout();
		InputStream stderr = sess.getStderr();

		byte[] buffer = new byte[8192];

		while (true)
		{
			if ((stdout.available() == 0) && (stderr.available() == 0))
			{
				/* Even though currently there is no data available, it may be that new data arrives
				 * and the session's underlying channel is closed before we call waitForCondition().
				 * This means that EOF and STDOUT_DATA (or STDERR_DATA, or both) may
				 * be set together.
				 */

				int conditions = sess.waitForCondition(ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA
						| ChannelCondition.EOF, this.getTimeout());

				/* Wait no longer than 1 hour (= 3600000 milliseconds) */

				if ((conditions & this.getTimeout()) != 0)
				{
					/* A timeout occured. */
					throw new IOException("Timeout while waiting for data from peer.");
				}

				/* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */

				if ((conditions & ChannelCondition.EOF) != 0)
				{
					/* The remote side won't send us further data... */

					if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0)
					{
						/* ... and we have consumed all data in the local arrival window. */
						break;
					}
				}

				/* OK, either STDOUT_DATA or STDERR_DATA (or both) is set. */

				// You can be paranoid and check that the library is not going nuts:
				// if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0)
				//	throw new IllegalStateException("Unexpected condition result (" + conditions + ")");
			}

			/* If you below replace "while" with "if", then the way the output appears on the local
			 * stdout and stder streams is more "balanced". Addtionally reducing the buffer size
			 * will also improve the interleaving, but performance will slightly suffer.
			 * OKOK, that all matters only if you get HUGE amounts of stdout and stderr data =)
			 */

			while (stdout.available() > 0)
			{
				int len = stdout.read(buffer);
				if (len > 0) // this check is somewhat paranoid
					out.write(buffer, 0, len);
			}

			while (stderr.available() > 0)
			{
				int len = stderr.read(buffer);
				if (len > 0) // this check is somewhat paranoid
					out.write(buffer, 0, len);
			}
		}
		/* Close this session */
		Integer exitStatus = null;
		do{
			exitStatus = sess.getExitStatus();
		}while(exitStatus == null);
		sess.close();

		/* Close the connection */

		conn.close();

		exitval = exitStatus.intValue();
		if(exitval == 0){
			this.m_message = "Success";
		}else{
			this.m_message = "Failure";
		}
		return exitval;
	}
	
	/**
	 * @param string
	 * @return
	 */
	private String encodeParameter(String string) {
		String retval = "";
		for(int i = 0;i < string.length();i++){
			if(string.charAt(i) == ' '){
				retval+= "\\ ";
			}else{
				retval+=string.charAt(i);
			}
		}
		return retval;
	}


	public static final String HOST = "host";
	public static final String USERNAME = "username";
	public static final String PASSWORD = "password";
	public static final String REMOTE_PROFILE = "remote_profile";
	public static final String REMOTE_PATH = "remote_path";
	public static final String TIMEOUT = "timeout";
	/**
	 * @param args
	 * @throws InputParamsException
	 */
	private void processArguments(String[] args) throws InputParamsException {
		try{
			for(int i = 0; i < 4;i++){
				String str = "JAVA-PARM"+(i+1);
				try{
					String val = System.getProperty(str);
					if(val != null){
						this.m_mnemonics.put(str, val);
					}
				}catch(Exception e){}
			}
		}catch(Exception e){}
		if(args.length < 2){
			throw new InputParamsException("Not enough arguments");
		}
		try {
			INIFileReader config_file = new INIFileReader(args[0]);
			try{
				this.m_host = this.replaceMnemonics(config_file.getValue("host"));
			}catch(Exception e){
				throw new InputParamsException("Missing 'host'");
			}
			try{
				this.m_username = this.replaceMnemonics(config_file.getValue("username"));
			}catch(Exception e){
				throw new InputParamsException("Missing 'username'");
			}
			try{
				this.m_password = this.replaceMnemonics(config_file.getValue("password"));
			}catch(Exception e){
				throw new InputParamsException("Missing 'password'");
			}
			try{
				this.m_remote_profile = this.replaceMnemonics(config_file.getValue("remote_profile"));
			}catch(Exception e){
				throw new InputParamsException("Missing 'remote_profile'");
			}
			try{
				this.m_remote_path = this.replaceMnemonics(config_file.getValue("remote_path"));
			}catch(Exception e){
				this.m_remote_path = "$MICRO/scripts/";
			}
			try{
				String timeout = this.replaceMnemonics(config_file.getValue("timeout"));
				this.m_timeout = Long.parseLong(timeout);
			}catch(Exception e){

			}
		} catch (Exception e) {
			throw new InputParamsException(e);
		}
	}
	public long getTimeout(){
		return this.m_timeout;
	}
	private static void showUsage(){
		System.out.println("Usage: config script");
		System.out.println();
		System.out.println("  config            Specifies the configuration file that has the");
		System.out.println("                    host information and password configuration.");
		System.out.println("  script [args...] Specifies the preconfigured script to run.");
		System.out.println("");
	}
}
